package com.kordar.admin.modular.sys.service.setting;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kordar.admin.enums.CommonActive;
import com.kordar.admin.enums.EnumUtils;
import com.kordar.admin.kernel.navbar.ForestHtmlInterface;
import com.kordar.admin.kernel.navbar.ForestInterface;
import com.kordar.admin.kernel.util.HtmlUtil;
import com.kordar.admin.kernel.util.RequestHolder;
import com.kordar.admin.modular.sys.entity.SettingEntity;
import com.kordar.admin.modular.sys.enums.AdminType;
import com.kordar.admin.modular.sys.enums.SettingPosition;
import com.kordar.admin.modular.sys.enums.SettingType;
import com.kordar.admin.modular.sys.service.SettingService;
import com.kordar.auth.Admin;
import com.kordar.rbac.AuthManager;
import com.kordar.rbac.Item;
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Scope("prototype")
public class SettingProcessor {
    final
    SettingService settingService;

    final
    AuthManager authManager;

    /**
     * [
     * "位置id" => [entity, entity...]
     * ]
     */
    Map<Integer, List<SettingEntity>> groups = new HashMap<>();

    public SettingProcessor(SettingService settingService, AuthManager authManager) {
        this.settingService = settingService;
        this.authManager = authManager;
        this.init();
    }

    private void init() {
        Set<Integer> anSet = EnumUtils.EnumToMapInt(SettingPosition.class).keySet();
        anSet.forEach(e -> {
            groups.put(e, new ArrayList<>());
        });  // 初始化groups列表

        Admin admin = (Admin) RequestHolder.getRequest().getAttribute("admin");
        Set<String> permissions = authManager.getPermissionsByUser(admin.getId()).stream().map(Item::getName).collect(Collectors.toSet());

        // 查询所有设置信息
        List<SettingEntity> list = settingService.list(new QueryWrapper<SettingEntity>()
                .eq("active", CommonActive.OPEN.getCode())
                .orderByDesc("sort")
        );
        for (SettingEntity entity : list) {
            Integer position = entity.getPosition(); // 通过position进行划分
            if (!admin.getType().equals(AdminType.TYPE_SUPER.getCode()) &&
                    entity.getPosition().equals(SettingPosition.POS_BODY_LEFT.getCode()) &&
                    StringUtils.hasText(entity.getUrl()) &&
                    !"#".equals(entity.getUrl())
            ) {
                if (!permissions.contains(entity.getUrl())) {
                    continue;
                }
            }
            groups.get(position).add(entity);
        }


        filterSide();

    }

    private void filterSide() {
        // 获取侧边栏
        Classification binaryTree = new Classification(groups.get(SettingPosition.POS_BODY_LEFT.getCode()));
        List<SettingEntity> list = groups.get(SettingPosition.POS_TOP_LEFT.getCode());
        Iterator<SettingEntity> iterator = list.iterator();
        while (iterator.hasNext()) {
            SettingEntity e = iterator.next();
            BinaryNode node = binaryTree.binaryTree(e.getId());
            if (e.getType().equals(SettingType.TYPE_CATEGORY.getCode()) && node == null) {
                iterator.remove();
            }
        }
    }

    public String output(Integer position, Map<String, String> options) throws UnsupportedEncodingException {

        if (position.equals(SettingPosition.POS_BODY_LEFT.getCode())) {
            StringBuilder sb = new StringBuilder();
            // 获取侧边栏
            Classification binaryTree = new Classification(groups.get(SettingPosition.POS_BODY_LEFT.getCode()));
            HashMap<Integer, LinkedList<BinaryNode>> binaryGroups = binaryTree.getGroups();
            List<SettingEntity> list = groups.get(SettingPosition.POS_TOP_LEFT.getCode());
            for (SettingEntity entity : list) {
                binaryTree.filterEmptyLeaf(entity.getId());
                BinaryNode node = binaryTree.binaryTree(entity.getId());
                if (node != null) {
                    options.put("navId", "CATE-" + entity.getId());
                    ForestHtml forestHtml = new ForestHtml(
                            binaryGroups, options, new ParentFinder(node, "", false).getHrefs()
                    );
                    sb.append(forestHtml.output(entity.getId()));
                }
            }

            return sb.toString();
        }

        Classification binaryTree = new Classification(groups.get(position));
        BinaryNode node = binaryTree.binaryTree(0);
        HashMap<Integer, LinkedList<BinaryNode>> binaryGroups = binaryTree.getGroups();
        ForestHtml forestHtml = new ForestHtml(
                binaryGroups, options, new ParentFinder(node, "", false).getHrefs()
        );
        return forestHtml.output(0);
    }

    private static class Classification {

        /**
         *
         */
        HashMap<Integer, LinkedList<BinaryNode>> groups = new HashMap<>();

        //
        public Classification(List<SettingEntity> data) {
            this.generateGroups(data);
        }

        public void filterEmptyLeaf(int root) {
            if (this.groups.get(root) == null) {
                return;
            }

            Iterator<BinaryNode> iterator = this.groups.get(root).iterator();
            while (iterator.hasNext()) {
                BinaryNode next = iterator.next();
                if (!hasGroupChild(next.getNode().getId())) {
                    if (!StringUtils.hasText(next.getNode().getUrl()) ||
                            "#".equals(next.getNode().getUrl())) {
                        iterator.remove();
                    }
                } else {
                    filterEmptyLeaf(next.getNode().getId());
                    if (!hasGroupChild(next.getNode().getId())) {
                        if (!StringUtils.hasText(next.getNode().getUrl()) ||
                                "#".equals(next.getNode().getUrl())) {
                            iterator.remove();
                        }
                    }
                }
            }
        }

        private void generateGroups(List<SettingEntity> data) {
            for (SettingEntity item : data) {
                Integer key = item.getPid();
                LinkedList<BinaryNode> queue = groups.getOrDefault(key, new LinkedList<>());
                queue.addLast(new BinaryNode(item));
                groups.put(key, queue);
            }
        }

        public HashMap<Integer, LinkedList<BinaryNode>> getGroups() {
            return this.groups;
        }

        private boolean hasGroupChild(Integer key) {
            return this.groups.containsKey(key);
        }

        public BinaryNode binaryTree(int root) {
            if (this.groups.get(root) == null) {
                return null;
            }
            LinkedList<BinaryNode> nodes = this.groups.get(root);
            return this.insertBinaryNode(nodes, 0);
        }

        private BinaryNode insertBinaryNode(LinkedList<BinaryNode> nodes, int index) {
            if (index >= nodes.size()) {
                return null;
            }
            BinaryNode node = nodes.get(index);
            if (node != null) {
                BinaryNode sibling = this.insertBinaryNode(nodes, index + 1);
                if (sibling != null) {
                    node.setRight(sibling);
                }
                Integer id = node.getNode().getId();
                if (this.hasGroupChild(id)) {
                    LinkedList<BinaryNode> children = this.groups.get(id);
                    BinaryNode child = this.insertBinaryNode(children, 0);
                    if (child != null) {
                        node.setLeft(child);
                    }
                }
            }
            return node;
        }
    }

    @Data
    @ToString
    private static class BinaryNode {

        private SettingEntity node;
        private BinaryNode left = null;
        private BinaryNode right = null;

        public BinaryNode(SettingEntity node) {
            this.node = node;
        }
    }

    static class ParentFinder {

        HashMap<Integer, String> hrefs = new HashMap<Integer, String>();

        public ParentFinder(BinaryNode tree, String href, boolean self) {
            this.preSearchParent(tree, href, self);
        }

        public HashMap<Integer, String> getHrefs() {
            return this.hrefs;
        }

        private boolean preSearchParent(BinaryNode node, String href, boolean self) {
            if (node == null) {
                return false;
            }

            SettingEntity item = node.getNode();
            if (href.equals(item.getUrl())) {
                if (self) {
                    this.hrefs.put(item.getId(), item.getUrl());
                }
                return true;
            }

            if (this.preSearchParent(node.getLeft(), href, self)) {
                this.hrefs.put(item.getId(), item.getUrl());
                return true;
            }

            return this.preSearchParent(node.getRight(), href, self);
        }
    }

    static class ForestHtml implements ForestInterface, ForestHtmlInterface {

        StringBuffer stringHtml = new StringBuffer();
        Map<String, String> options;
        Map<Integer, String> actives;
        HashMap<Integer, LinkedList<BinaryNode>> groups;
        String type;

        public ForestHtml(HashMap<Integer, LinkedList<BinaryNode>> groups, Map<String, String> options, Map<Integer, String> actives) {
            this.type = options.get("type");
            this.actives = actives;
            this.groups = groups;
            this.options = options;
        }

        public String beginChildren() {
            return "<dl class=\"layui-nav-child\">";
        }

        public String endChildren() {
            return "</dl>";
        }

        /**
         * 'left' => 'layui-layout-left',
         * 'right' => 'layui-layout-right',
         * 'vertical' => 'layui-nav-tree',
         * 'sidebar' => 'layui-nav-tree layui-nav-side'
         */
        public String output(int rootKey) throws UnsupportedEncodingException {

            if (this.groups.containsKey(rootKey)) {
                LinkedList<BinaryNode> rootNodes = this.groups.get(rootKey);
                this.generateHtmlTree(rootNodes, 0);
            }
            Map<String, String> options = new HashMap<String, String>();
            options.put("class", "layui-nav " + this.options.get("layout"));
            if ("side".equals(type)) {
                options.put("lay-shrink", "all");
                options.put("nav-id", this.options.remove("navId"));
                options.put("lay-filter", "admin-side-nav");
                options.put("style", "margin-top: 15px;");
            }
            if ("left".equals(type)) {
                // options.put("lay-filter", "admin-side-nav");
            }
            if ("right".equals(type)) {
                options.put("lay-filter", "layadmin-layout-right");
            }
            return HtmlUtil.tag("ul", stringHtml.toString(), options);
        }

        // 生成Html树
        protected void generateHtmlTree(LinkedList<BinaryNode> nodes, int depth) throws UnsupportedEncodingException {
            for (BinaryNode node : nodes) {
                stringHtml.append(beginItem(node, depth));
                int id = node.getNode().getId();
                if (groups.containsKey(id)) {
                    LinkedList<BinaryNode> children = groups.get(id);
                    stringHtml.append(beginChildren());
                    generateHtmlTree(children, depth + 1);
                    stringHtml.append(endChildren());
                }
                stringHtml.append(endItem(node, depth));
            }
        }

        private String beginItem(BinaryNode node, Integer depth) throws UnsupportedEncodingException {
            SettingEntity setting = node.getNode();
            String href;
            if ("side".equals(type)) {
                href = this.href(setting, depth == 0);
            } else {
                href = this.href(setting, false);
            }

            if (setting.getType().equals(SettingType.TYPE_CATEGORY.getCode())) {
                return depth == 0 ? "<li class=\"layui-nav-item layui-hide-xs\" lay-unselect=\"\">" + href : "<dd>" + href;
            }

            return depth == 0 ? "<li class=\"layui-nav-item\">" + href : "<dd>" + href;
        }

        private String endItem(BinaryNode node, Integer depth) {
            return depth == 0 ? "</li>" : "</dd>";
        }

        protected String href(SettingEntity setting, boolean cite) throws UnsupportedEncodingException {

            String href = "#".equals(setting.getUrl()) || !StringUtils.hasText(setting.getUrl()) ? "" : setting.getUrl();
            HashMap<String, String> options = new HashMap<String, String>();
            String title = setting.getTitle();
            String event = setting.getEvent();
            int type = setting.getType();
            String icon = setting.getIcon();
            options.put("data-url", href);
            options.put("data-label", title);

            if (type == SettingType.TYPE_EVENT.getCode()) {
                if (StringUtils.hasText(event)) {
                    options.put("ew-event", setting.getEvent());
                    options.put("title", title);
                    return HtmlUtil.layA("<i class='layui-icon " + icon + "'></i> ", title, options);
                } else {
                    return "";
                }
            }

            if (cite) {
                options.put("lay-direction", "2");
                title = "<cite>" + title + "</cite>";
            }

            if (type == SettingType.TYPE_NORMAL.getCode()) {
                return HtmlUtil.layA("<i class=\"layui-icon " + icon + "\"></i> " + title, href, options);
            }

            if (type == SettingType.TYPE_CATEGORY.getCode()) {
                options.put("nav-bind", "CATE-" + setting.getId());
                return HtmlUtil.layA(title, "", options);
            }

            if (type == SettingType.TYPE_JUST_ICON.getCode()) {
                options.put("ew-event", setting.getEvent());
                return HtmlUtil.layA("<i class='layui-icon " + icon + "'></i> ", "", options);
            }

            if (type == SettingType.TYPE_JUST_TEXT.getCode()) {
                return HtmlUtil.layA(title, href, new HashMap<>());
            }

            if (type == SettingType.TYPE_CUSTOMER.getCode()) {
                if (!StringUtils.hasText(setting.getEvent())) {
                    return "";
                }

                try {
                    Class<?> classz = Class.forName(setting.getEvent());
                    ProcessorElement instance = (ProcessorElement) classz.newInstance();
                    return instance.render(setting, href, options);
                } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                    e.printStackTrace();
                    return "";
                }
            }

            return "";
        }

    }


}
